/*
 * Copyright (C) 2010, JDevelopers (jdevelopers@my-pages.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdevelopers.xarf;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import org.jdevelopers.xarf.exceptions.InvalidSchemaException;
import org.jdevelopers.xarf.exceptions.InvalidSchemaLocationException;
import org.jdevelopers.xarf.exceptions.SchemaReadException;
import org.jdevelopers.xarf.util.ClassFactory;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

/**
 * Factory which parses the JSON-content into a XarfSchema-object.
 * 
 * @author Marcel Hartwig
 * @version 0.2
 * @since JXarfValidator 0.1
 */
public class XarfSchemaFactory {
	
	/**
	 * Private constructor, because it is not needed.
	 */
	private XarfSchemaFactory() {
	}
	
	/**
	 * Parses the JSON-schema and maps it into a XarfSchema for further use.
	 * 
	 * @param schemaLocation String, which represents the location of the schema.
	 * @return The parsed JSON-schema
	 * @throws ClassNotFoundException if there is a class type set in JSON-schema, for which is no Java-Class available.
	 * @throws InvalidSchemaException if the schema is invalid.
	 * @throws InvalidSchemaLocationException if the given schema location is invalid.
	 * @throws SchemaReadException if there is some trouble while reading the schema from the schema location.
	 */
	public static XarfSchema getXarfSchema(String schemaLocation) throws InvalidSchemaLocationException, SchemaReadException, ClassNotFoundException, InvalidSchemaException {
		//load the content of the given schema location
		URLConnection connection = null;
		try {
			connection = new URL(schemaLocation).openConnection();
		} catch (MalformedURLException e) {
			//this is not a valid URI
			throw new InvalidSchemaLocationException(schemaLocation);
		} catch (IOException e) {
			//error occurred while trying to connect to web server
			throw new SchemaReadException(schemaLocation);
		}
		
		String fileContent = null;
		try {
			fileContent = new Scanner(connection.getInputStream()).useDelimiter("\\Z").next();
		} catch (IOException e) {
			//error while reading the content
			throw new SchemaReadException(schemaLocation);
		}
		
		//parse the JSON-content into Java-objects
		JSONObject jsonObject = (JSONObject) JSONValue.parse(fileContent);
		
		XarfSchema xarfSchema = new XarfSchemaImpl();
		xarfSchema.setDescription((String)jsonObject.get("description"));
		
		//setting the description of the schema
		JSONObject schemaProperties = (JSONObject) jsonObject.get("properties");
		
		XarfSchemaProperty property;
		
		//this map contains the name of the property and the name of the required property, if there is one required
		Map<String, String> requiredPropertyMap = new HashMap<String, String>();
		
		//setting the properties of the schema
		for(Object propertyName : schemaProperties.keySet()) {
			property = new XarfSchemaPropertyImpl();
			//setting the property name
			property.setPropertyName((String)propertyName);
			
			//setting some options of the property
			JSONObject propertyValues = (JSONObject) schemaProperties.get(property.getPropertyName());
			
			//is the property optional?
			if(propertyValues.get("optional") != null) {
				property.setOptional(true);
			}
			
			if(propertyValues.get("multiple") != null) {
				property.setMultipleOccurrence(true);
			}
			
			//content type of the property
			property.setPropertyType(ClassFactory.getClassFromName((String)propertyValues.get("type")));
			
			//if there are some limitations regarding the value of, then here were the possible values be set
			if(propertyValues.get("enum") != null) {
				JSONArray enumValues = (JSONArray) propertyValues.get("enum"); 
				for(int i = 0; i < enumValues.size(); ++i) {
					property.addPossibleValue((String)enumValues.get(i));
				}
			}
			
			//setting the type of the property
			if(propertyValues.get("format") != null) {
				property.setPropertyFormatType(EXarfPropertyFormatType.getByValue((String)propertyValues.get("format")));
			}
			
			//are there any other properties required for this property?
			if(propertyValues.get("requires") != null) {
				requiredPropertyMap.put(property.getPropertyName(), (String)propertyValues.get("requires"));
			}
			
			xarfSchema.addxarfProperty(property);
		}
		
		//setting the required properties
		for(String propertyName : requiredPropertyMap.keySet()) {
			property = xarfSchema.getXarfProperty(propertyName);
			property.setRequiredProperty(xarfSchema.getXarfProperty(requiredPropertyMap.get(propertyName)));
			if(property.getRequiredProperty() == null) {
				throw new InvalidSchemaException("Schema not valid");
			}
		}
		
		//checking the parsed JSON-structure
		checkSchemaStructure(xarfSchema);
		
		return xarfSchema;
	}
	
	/**
	 * Checks the parsed JSON-structure, if it is a correct X-Arf Schema.
	 *  
	 * @param schema The parsed JSON-structure.
	 * @throws InvalidSchemaException If the parsed schema is not correct.
	 */
	private static void checkSchemaStructure(XarfSchema schema) throws InvalidSchemaException {
		//check, if there is a description for the schema
		if(schema.getDescription() == null || schema.getDescription().length() == 0) {
			throw new InvalidSchemaException("Schema has no description!");
		}
		
		//check, if the schema has properties
		if(schema.hasXarfProperties()) {
			throw new InvalidSchemaException("Schema has no properties!");
		}
		
		//check for valid properties
		for(XarfSchemaProperty property : schema.getXarfProperties()) {
			if(property == null) {
				throw new InvalidSchemaException("Schema has invalid properties!");
			}
		}
	}
	
}
